use crate::{
    helpers::{create_ttr, link_finder, redirect_frontends, wrap_url, Bot, Config},
    keyboards,
    // models::{Post, User, UserPost},
};
use anyhow::{Context, Result};
use db::{models::Post, Db};
use tokio_stream::StreamExt;
use tracing::info;
use futures::stream::FuturesUnordered;
use urlexpand::unshorten;

use std::{convert::TryInto, sync::Arc, time::Duration};

use teloxide::{
    prelude::*,
    utils::markdown::{escape, escape_link_url},
};

/// Add new url to the db and belong it to user
pub async fn add(bot: Bot, m: Message, db: Db, config: Config) -> Result<()> {
    let urls = link_finder(&m);
    if urls.is_empty() {
        info!("No urls in the message!");
    }
    // let bot = Arc::new(Mutex::new(bot));
    let telegram_user = m.from().context("Error while get telegram user data:")?;
    let telegram_user_id = telegram_user.id;
    let _user_username = telegram_user.username.clone();
    // let user_id = db::models::MatrixOrTelegramId::TelegramId{id: telegram_user_id, username: user_username};
    let user = db.get_user_id_tg(&telegram_user_id).await?;
    let mut urls = tokio_stream::iter(urls);
    // for mut url in urls {
        let mut handlers = Vec::new();
    while let Some(url) = urls.next().await {
        let db = db.clone();
        let config = config.clone();
        let bot = bot.clone();
        let handle: tokio::task::JoinHandle<Result<()>> = tokio::spawn(async move {
            // println!("test");
            let cleaner = clearurl::UrlCleaner::from_file(&config.rules_file).await?;
            cleaner.clear(url.as_str()).await;
            // remove_tracking_parameters(&mut url);
            let page_data = get_real_url(&url, &config.proxy).await;
            // remove_tracking_parameters(&mut real_url);
            // If post exists, see if it belongs to the user.
            let post = {
                let real_url_str = page_data.url.as_str();
                match db
                    .find_post(real_url_str)
                    .await
                    .context("cannot find post")?
                {
                    Some(post) => post,
                    None => {
                        let post = create_post(page_data, config).await?;
                        db.create_post(&post).await.context("cannot create post")?
                    }
                }
            };
            // info!("Post after found: {:#?}", post);
            let user_post = {
                match db
                    .find_user_post(user, post.id())
                    .await
                    .context("cannot find user post")?
                {
                    Some(user_post) => user_post,
                    None => db
                        .create_user_post(post.id(), url.as_str(), user)
                        .await
                        .context("cannot create user post")?,
                }
            };
            let mut answer = {
                let url_str = url.as_str();
                if let Some(title) = post.title() {
                    wrap_url(url_str, &title)
                } else {
                    escape(&escape_link_url(url_str))
                }
            };
            // let ttr = count_ttr(post.word_count());
            if let Some(ttr) = post.word_count() {
                create_ttr(&mut answer, ttr.try_into().unwrap())
            };
            bot
                // .lock()
                // .await
                // .deref()
                .send_message(telegram_user_id, answer)
                .reply_markup(keyboards::standart_keyboard(
                    &user_post.id().hyphenated().to_string(),
                    user_post.read(),
                    user_post.favorite(),
                    user_post.show_real_url,
                ))
                .await?;
            // if let Ok(post_id) = post_exists(&real_url, &db).await {
            //     if let Some(user_post_info) =
            //         check_duplicates_in_post_map(user_id, post_id, &db).await?
            //     {
            //         bot.send_message(telegram_user.id, url.to_string())
            //             .reply_markup(keyboards::standart_keyboard(
            //                 &post_id.hyphenated().to_string(),
            //                 user_post_info.read,
            //                 user_post_info.favorite,
            //             ))
            //             .await?;
            //     }
            //     // If post does not exist, create it
            // } else {
            //     let post = create_post(real_url).await?;
            //     save_post_to_db(&post, &db).await?;
            //     append_post_to_user(user_id, post.id(), &db, &url).await?;
            //     let _post_id = post.id();

            //     log::info!("Successful saved post");
            //     let mut answer = {
            //         let url_str = url.as_str();
            //         if let Some(title) = post.title() {
            //             wrap_url(url_str, &title)
            //         } else {
            //             escape(&escape_link_url(url_str))
            //         }
            //     };
            //     let ttr = count_ttr(post.word_count);
            //     if let Some(ttr) = ttr {
            //         create_ttr(&mut answer, ttr)
            //     };
            //     bot.send_message(telegram_user.id, answer)
            //         .reply_markup(keyboards::standart_keyboard(
            //             &post.id().hyphenated().to_string(),
            //             false,
            //             false,
            //         ))
            //         //   .parse_mode(ParseMode::MarkdownV2)
            //         .await?;

            Ok(())
            // }
        });
       // task.await??;
       handlers.push(handle);
    }
    handlers.iter().collect::<FuturesUnordered<_>>();
    for handle in handlers {
        let _ = handle.await??;
    }

    Ok(())
}
use url::Url;
pub struct FetchPage {
    url: Url,
    html: Option<Vec<u8>>,
}
async fn get_real_url(original_url: &Url, proxy: &str) -> FetchPage {
    let proxy = reqwest::Proxy::https(proxy).unwrap();
    let timeout = Duration::from_secs(10);
    let mut real_url = None;
    let mut html = None;
    // match un
    if let Ok(url) = unshorten(original_url.as_str(), Some(timeout), Some(proxy.clone())).await {
        real_url = Some(Url::parse(&url).unwrap());
    }
    let request = reqwest::Client::builder()
        .proxy(proxy)
        .build()
        .unwrap()
        .get(original_url.to_owned())
        .timeout(timeout)
        .send()
        .await;
    if let Ok(res) = request {
        if real_url.is_none() {
            real_url = Some(res.url().to_owned())
        }
        let res_text = res.text().await.unwrap();
        html = Some(res_text.as_bytes().to_owned());
    }
    FetchPage {
        url: {
            match real_url {
                Some(url) => url,
                None => original_url.to_owned(),
            }
        },
        html,
    }
}
use time::OffsetDateTime;

fn created() -> OffsetDateTime {
    OffsetDateTime::now_utc()
}

pub async fn create_post(page_data: FetchPage, config: Config) -> Result<Post> {
    let created = created();
    let id = uuid::Uuid::new_v4();
    let mut title = None;
    let mut word_count = None;
    let alternative_url = redirect_frontends(&page_data.url).map(|s| s.to_string());
    //let MercuryReturn { title, word_count } = parse(&real_url).context("NoneError")?;
    let url = Arc::new(page_data.url);
    match parser::parse(
        url.clone(),
      /*   page_data.html
        .unwrap()
        .as_mut_slice(),*/ &config.mercury_path,
        &config.readability_path,
    )
    .await
    {
        Ok(output) => {
            // info!("parser output: {:#?}", output);
            title = output.title;
            word_count = output.duration;
        }
        Err(e) => {
            info!("parser error! {:#?}", e);
        }
    };
    let post = Post::new(
        id,
        title,
        word_count.map(|s| s.seconds() as i64),
        url.clone().to_string(),
        alternative_url,
        created,
    );
    // let post = Post::builder()
    //     .id(id)
    //     .title(title)
    //     .word_count(word_count.map(|s|s.seconds() as i64))
    //     .real_url(real_url.to_string())
    //     .created(created)
    //     .alternative_url(alternative_url)
    //     .build();
    Ok(post)
}
